package org.gldap;


import java.io.File;
import java.io.InputStreamReader;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.StringTokenizer;

import javax.naming.InvalidNameException;
import javax.naming.directory.ModificationItem;

import org.apache.directory.server.core.*;
import org.apache.directory.server.core.entry.*;
import org.apache.directory.server.core.filtering.EntryFilteringCursor;
import org.apache.directory.server.core.partition.*;
import org.apache.directory.server.core.partition.impl.btree.jdbm.JdbmIndex;
import org.apache.directory.server.core.partition.impl.btree.jdbm.JdbmPartition;
import org.apache.directory.server.ldap.LdapService;
import org.apache.directory.server.protocol.shared.SocketAcceptor;
import org.apache.directory.server.xdbm.Index;
import org.apache.directory.shared.ldap.codec.modify.ModifyRequest;
import org.apache.directory.shared.ldap.entry.Entry;
import org.apache.directory.shared.ldap.entry.Modification;
import org.apache.directory.shared.ldap.entry.ModificationOperation;
import org.apache.directory.shared.ldap.exception.*;
import org.apache.directory.shared.ldap.filter.ExprNode;
import org.apache.directory.shared.ldap.filter.FilterParser;
import org.apache.directory.shared.ldap.filter.SearchScope;
import org.apache.directory.shared.ldap.message.AliasDerefMode;
import org.apache.directory.shared.ldap.message.SearchRequest;
import org.apache.directory.shared.ldap.name.*;
import org.apache.directory.shared.ldap.schema.AttributeType;
import org.apache.log4j.Logger;

public class EmbeddedADS {
	
	private static Logger log4j = Logger.getLogger(EmbeddedADS.class);
	private DirectoryService service;
	private SocketAcceptor socketAcceptor;
	private LdapService ldapService;
	private String D_SUFFIX = "dc=apache,dc=org";
	private String A_SUFFIX = "ou=Addressbook," + D_SUFFIX;
	public EmbeddedADS() throws Exception
	{
		init();
	}
	protected void finalize() {
		this.killService();
	}
	public void killService(){
		try{
			ldapService.stop();
			service.shutdown();
		} catch (Exception e){
			throw new RuntimeException(e);
		}
	}
	public void fireupService() throws Exception{
			service.setShutdownHookEnabled(true);
			socketAcceptor = new SocketAcceptor(null);
			ldapService = new LdapService();
			ldapService.setSocketAcceptor(socketAcceptor);
			ldapService.setDirectoryService(service);
	        service.setWorkingDirectory( new File( System.getProperty("java.io.tmpdir")+ File.separator + "gldap"));
			ldapService.setIpPort(10389);		
			service.startup();
			ldapService.start();
	}
	private void init() throws Exception {
		// Initialise the LDAP service
		service = new DefaultDirectoryService();
		// Disable the ChangeLog system
		service.getChangeLog().setEnabled(false);
		// create a new partition named 'Apache'
		Partition apachePartition = addPartition("apache",D_SUFFIX);
		// Index some attributes on the Apache partition
		addIndex(apachePartition, "objectClass", "ou","uid");
		 // and start the service
		//service.startup();
		this.fireupService();
		//this.clearAll();

		try
		{
			service.getAdminSession().lookup(apachePartition.getSuffixDn());
			
		}
		catch (LdapNameNotFoundException lnnfe)
		{
			LdapDN directoryRoot = new LdapDN(D_SUFFIX);
			ServerEntry entryApache = service.newEntry(directoryRoot);
			entryApache.add("objectClass","organization","dcObject");
			entryApache.add("dc","Apache");
			entryApache.add("o","Apache Directory Service");
			service.getAdminSession().add(entryApache);
			
			LdapDN ou = new LdapDN(A_SUFFIX);
			ServerEntry ouEntry = service.newEntry(ou);
			ouEntry.add("ou","Addressbook");
			ouEntry.add("objectClass","top");
			ouEntry.add("objectClass", "organizationalUnit");
			service.getAdminSession().add(ouEntry);
			
			/*LdapDN dummyEntry = new LdapDN("cn=Brown,ou=Addressbook,dc=apache,dc=org");
			ServerEntry dcEntry = service.newEntry(dummyEntry);
			dcEntry.add("cn","Brown");
			dcEntry.add("objectClass","person","organizationalPerson","top","inetOrgPerson");
			dcEntry.add("mail" , "test@mail.com");
			dcEntry.add("telephoneNumber","9898798798798");
			dcEntry.add("sn","Brown");
			dcEntry.add("givenName","Brown given name");
			service.getAdminSession().add(dcEntry);*/
			//insertContact("Brown, James", "brown@email.com");
		}
		//listAllContacts();
	}
	private Entry getContact(String _title){
		Entry contact = null;
		try {
			contact = service.getAdminSession().lookup(new LdapDN("cn="+ _title +"," + A_SUFFIX ));
		} catch (InvalidNameException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch ( LdapNameNotFoundException e){
			log4j.warn("contact not found");
			return null;
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return contact; 
	
	}
	public void insertContact( String _title2) throws Exception{
		
		String _title = fixTitle( _title2 );
		LdapDN contactDN = new LdapDN("cn=" + _title + "," + A_SUFFIX );
		if ( service.getAdminSession().exists( contactDN )){
			log4j.warn("contact: " + _title2 + " already exists");
			return;
		}
		try{
			
			ServerEntry contactEntry = service.newEntry(contactDN);
			contactEntry.add("cn",_title);
			contactEntry.add("objectClass","person","organizationalPerson","top","inetOrgPerson");
			//contactEntry.add("objectClass","person","organizationalPerson","top");

			//contactEntry.add("mail" , _email);
			//dcEntry.add("telephoneNumber",  );
			contactEntry.add("sn",_title);
			//contactEntry.add("givenName",_title);
			service.getAdminSession().add(contactEntry);
		} catch (Exception e) {
			// TODO: handle exception
			log4j.error("insert contact "  + _title  +" failed \n " + e.getMessage() );
		}
	}	
	
	
	public void addContactAttribute( String _title ,  String _key, String _val) throws Exception{
		String cn = this.fixTitle(_title); 
		log4j.warn("contact: " + cn + " ,adding attribute: " + _key + " , value: " + _val  );
		LdapDN contactDN = new LdapDN("cn="+ cn + "," + A_SUFFIX);
		// verify that this guy/entry exists.
		if ( ! service.getAdminSession().exists( contactDN ))
			throw ( new Exception("entry cn:" + cn + " does not exists."));
		
		Entry contact = getContact(cn);
		/*if (  contact.get(_key).contains(_val) ){
			log4j.warn(cn + " already has " + _key +" : " + _val);
			return;
		}*/
		
		AttributeType atType;
		Modification m = null;
		try {
			atType = service.getRegistries().getAttributeTypeRegistry().lookup(_key);
			ServerAttribute attr = new DefaultServerAttribute(atType, _val);
			if ( contact.contains(_key, _val)) 
				return;
			/*if (  contact.contains(attr) && ! contact.get(_key).getString().equals(_val)){
				if ( ! contact.get(_key).getString().equals(_val) ) {
					log4j.warn(cn + " " + _key + " is: " +   contact.get(_key).getString() );
				}
				log4j.warn(cn + " already has " + _key +" : " + _val  + " details: " + contact.get(_key).getString() );
				return;
			}*/
			m = new ServerModification(ModificationOperation.ADD_ATTRIBUTE , attr);
			
		} catch ( Exception e){
			log4j.error(e.getMessage());
		}
	    List<Modification> modifications = new ArrayList<Modification>( 1 );
		modifications.add(m);
		try {
			service.getAdminSession().modify(contactDN, modifications);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	private String fixTitle(String _title ){
		// if the title contains a comma "," then swap first name with last name and remove the comma.
		String[] a = null;
		String b = "";
		if ( _title.contains(",")){
			StringTokenizer st = new StringTokenizer(_title, ",");
			a = new String[st.countTokens()];
			for ( int i = 0; st.hasMoreTokens() ; i++){
				a[i] = st.nextToken();
			}
			
			for (int i = a.length; i > 0; i--){
				b = b + a[i - 1] + " ";
			}
			return b;
		}
		return _title;
	}
	
	
	public void clearAll() throws InvalidNameException, Exception{
		System.out.println("clear all entries\n");
		ExprNode filter = FilterParser.parse("(objectClass=*)");
		EntryFilteringCursor cursor;
		try {
			cursor = service.getAdminSession().search(new LdapDN(A_SUFFIX) , SearchScope.SUBTREE , filter, AliasDerefMode.DEREF_ALWAYS, null);
		} catch( Exception e) { 
			e.printStackTrace();
			return;
		}
		
        while ( cursor.next() )
        {
            ServerEntry serverEntry = cursor.get().getOriginalEntry();
            System.out.println( serverEntry.getDn() );
            service.getAdminSession().delete(serverEntry.getDn());
            //entries.add( convertToOlmEntry( serverEntry ) );
        }


		service.getAdminSession().delete(new LdapDN(A_SUFFIX));
		service.getAdminSession().delete(new LdapDN(D_SUFFIX));
	}
	
	public void listAllContacts() throws InvalidNameException, Exception{
		System.out.println("listing all entries\n");
		ExprNode filter = FilterParser.parse("(objectClass=*)");
		EntryFilteringCursor cursor = service.getAdminSession().search(new LdapDN(A_SUFFIX) , SearchScope.SUBTREE , filter, AliasDerefMode.DEREF_ALWAYS, null);

		
        while ( cursor.next() )
        {
            ServerEntry serverEntry = cursor.get().getOriginalEntry();
            System.out.println( serverEntry.getDn() );
        }
	}
	
	
	public static void main( String[] args) // throws Exception
	{
		try
		{
			//create the server
			EmbeddedADS ads = new EmbeddedADS();
			//read an entry
			Entry result = ads.service.getAdminSession().lookup(new LdapDN(ads.D_SUFFIX));
			
			System.out.println("Found entry: "+ result);
			
			Entry result2 = ads.service.getAdminSession().lookup(new LdapDN(ads.A_SUFFIX));
			System.out.println(result2);
			
			
			
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	private Partition addPartition(String partitionId, String partitionDn) throws Exception
	{
		// Create a new partition named 'foo'
		Partition partition = new JdbmPartition();
		partition.setId( partitionId);
		partition.setSuffix(partitionDn);
		service.addPartition(partition);
		return partition;
		
	}
	private void addIndex(Partition partition , String... attrs)
	{
		// Index some attributes on the apache partition
		HashSet<Index<?, ServerEntry>> indexedAttributes = new HashSet<Index<?, ServerEntry>>();
		for ( String attribute:attrs)
		{
			indexedAttributes.add(new JdbmIndex<String, ServerEntry>(attribute));
		}
		((JdbmPartition) partition).setIndexedAttributes(indexedAttributes);
		}
}
